# -*- coding:utf-8 -*- 

from cn.pigersing.PyShare.Event import *
from cn.pigersing.PyShare.PyShareEvents import *
from cn.pigersing.PyShare.Services.Chat import Chatting
from cn.pigersing.PyShare.Services.FileShare import FileShare
from cn.pigersing.PyShare.Services.ServiceBase import ServiceRoot
from cn.pigersing.PyShare.Services.ServiceExceptions import *
from cn.pigersing.PyShare.Services.Session import *
from cn.pigersing.PyShare.utils.LogPrint import get_logger
from cn.pigersing.PyShare.utils.UdpMCast import get_ip_address
from cn.pigersing.jsonrpc.SimpleJSONRPCServer import SimpleJSONRPCRequestHandler, \
    ThreadPoolJSONRPCServer
from random import choice
from string import printable
from xmlrpclib import Fault
import cgi
import cn.pigersing.Zeroconf as Zeroconf
import os
import posixpath
import socket
import threading
import urllib
import urlparse
import uuid


logger = get_logger('ServiceServer')

PROTOCAL = '_pyshare._tcp.local.'
# define the code length of secret 
CODE_LENGTH = 16


def get_id(server_name):
    '''return the remote server id'''
    return server_name.split(PROTOCAL)[0][: - 1]
        

def random_string(length):
    """
    Returns a random string of letters with the given length.
    """
    chars = []
    letters = printable[: - 7]
    
    while length > 0:
        length -= 1
        chars.append(choice(letters))
    return ''.join(chars)


class PyShareServiceHanlder(SimpleJSONRPCRequestHandler): 
    
    server_version = 'PyShare Server'

    def do_GET(self):
        try :
            sessionid, taskname, taskid, extra = self.translate_path()
            session = self.server.session_manager.get_session(sessionid)
            if session :
                # generate a event and dispatch to listener
                session.post_event(Evt_GET_Task(request=self,
                                                task_name=taskname,
                                                task_id=taskid,
                                                extra=extra),
                                   directly=True)
            else : 
                raise ValueError('Can not find such session %s' % sessionid)
        except Exception, why:
            logger.debug(why)
            try :
                self.report_404('%s' % why)
            except : pass
            

    def translate_path(self):
        
        # abandon query parameters
        path = urlparse.urlparse(self.path)[2]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        l = len(words)
        if l not in (3, 4) :
            raise ValueError('Invalid path %s' % path)
        if l == 3:
            words.extend("")
        return words


class PyShareServiceServer(ThreadPoolJSONRPCServer):
    
    def __init__(self, addr, session_manager, threadpool = None, log=False):
        from cn.pigersing.threadpool import ThreadPool
        if not threadpool :
            threadpool = ThreadPool(10)
        ThreadPoolJSONRPCServer.__init__(self, threadpool, addr, requestHandler=PyShareServiceHanlder, logRequests=False)
        self.session_manager = session_manager


class ServerManager(SessionManager, Session):
    
    def __init__(self):
        
        # initialize the SessionManager class
        SessionManager.__init__(self)
        # initialize the session class
        Session.__init__(self)
        # initialize shut down event
        self._is_running = False
        #
        self.built_services()
    
    def built_services(self):
        # build services directory
        root = ServiceRoot(self)
        root.AddChild(Chatting())
        root.AddChild(FileShare())
        # set services
        self.services = root
        #
        # add self to session manager
        self.id = str(uuid.uuid1().get_hex())
        self.add_session(self)
    
    def create_server(self):
        if self.is_running :  raise Exception('Server is running, Please shutdown first')
        # set up servers
        self.service_server = PyShareServiceServer(("", socket.INADDR_ANY), self)
        self.broadcast_server = Zeroconf.Zeroconf()
        # register handles for broadcasting server
        self.broadcast_server.addServiceListener(PROTOCAL, self)
        #
        # register the service instance
        self.service_server.register_instance(self.services)
    
    @property
    def is_running(self):
        return self._is_running
    
    
    def removeService(self, server, type, name):
        id = get_id(name)
        if  self.is_contained(id) :
            session = self.get_session(id)
            self.remove_session(id)
            self.post_event(Evt_Peer_Leaved(session=session))
            logger.debug('Leaved a peer :%s' % session.to_xml())

    def addService(self, server, type, name):
        id = get_id(name)
        if not self.is_contained(id) :
            # Request more information about the service
            info = server.getServiceInfo(type, name)
            prop = info.getProperties()
            session = Session(prop['id'], prop['url'])
            session.nickname = prop['nickname']
            # set session's private key
            private_key = random_string(CODE_LENGTH)
            session.set_crt_key(private_key, prop['public key'])
            self.add_session(session)
            # trigger the event
            self.post_event(Evt_Peer_Joined(session=session))
            logger.debug('Added a peer :%s' % prop)
    
    def start_server(self):
        ''' start severing forever until shutdown is called'''

        # create services server, providing JSON-RPC
        self.create_server()
        try :
            self.__shutdown_event = threading.Event()
            ss = threading.Thread(name='s_server', target=self.service_server.start)
            ss.setDaemon(True)
            ss.start()
            #
            # create broadcast server for service discovering
            port = self.service_server.server_address[1]
            local_ip = get_ip_address()
            self.url = 'http://' + local_ip + ':' + str(port)
            self.public_key = random_string(CODE_LENGTH)
            local_ip = socket.inet_aton(local_ip)
            svc = Zeroconf.ServiceInfo(PROTOCAL,
                                       '%s.%s' % (self.id, PROTOCAL),
                                       address=local_ip,
                                       port=port,
                                       weight=0, priority=0,
                                       properties={'id':self.id,
                                                  'url':self.url,
                                                  'nickname':self.nickname_utf8(),
                                                  'public key': self.public_key, 
                                                  }
                                      )
            self.broadcast_server.registerService(svc)
            self._is_running = True
            logger.debug('server started! id : %s, url: %s' % (self.id, self.url))
        except :
            self.service_server.shutdown()
            self.broadcast_server.close()
            self._is_running = False
            raise
                    
        
    def shutdown(self):
        '''close current server'''
        # simply set the shutdown event
        if self.is_running:
            self.service_server.shutdown()
            self.broadcast_server.close()
            # remove all session from current session dictionary
            self.removeall_session()
            self.add_session(self)
            self._is_running = False
            logger.debug('server shutdown! id : %s' % self.id)
            
        
#------------------------------------------------------------------------------ 
# test code
#------------------------------------------------------------------------------ 
if __name__ == '__main__' :
    
    import random
    import time
    
    def tester(lt):
        server = ServerManager()
        server.start_server()
        
        timer = threading.Timer(lt, server.shutdown)
            
    
    for i in xrange(3) :
        
        tl = random.randint(15, 30)
        
        t = threading.Thread(target=tester, args=(tl,))
        t.start()
    
        
